﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace ZProto
{
    public class Parser
    {
        private string _buffer;
        private int _index;
        private int _length;
        private int _current;
        private int _line;
        private StringBuilder _builder;
        static char[] Signs = new[] { ':', '{', '}', '[', ']' };
        static char[] WhiteSpaces = new[] { ' ', '\r', '\n', '\t' };

        private Parser(string buffer)
        {
            _buffer = buffer;
            _index = -1;
            _length = buffer.Length;
            _line = 1;
            _builder = new StringBuilder();
        }
        private enum TokenType
        {
            // 没有
            None,
            // 名称
            Lable,
            // 符号
            Sign,
        }
        private struct Token
        {
            public string Value;
            public TokenType Type;
        }
        public static Proto ParseAtPath(string path)
        {
            var parser = new Parser(File.ReadAllText(path));
            return parser.DoParse();
        }

        public static Proto ParseAtText(string buffer)
        {
            var parser = new Parser(buffer);
            return parser.DoParse();
        }

        private Proto DoParse()
        {
            List<ProtoType> types = new List<ProtoType>();
            while (true)
            {
                if(CheckProtoType(out var type))
                {
                    types.Add(BuildProtoType(type));
                    continue;
                }
                if (_current != -1)
                    throw new Exception($"语法错误！行:{_line}");
                break;
            }
            return new Proto(types.ToArray());
        }
        private bool CheckProtoType(out string type)
        {
            var tk = GetToken();
            if (tk.Type == TokenType.Lable)
            {
                type = tk.Value;
                return type == "class" || type == "struct";
            }
            type = null;
            return false;
        }
        private ProtoType BuildProtoType(string type)
        {
            List<ProtoField> fields = new List<ProtoField>();
            if (!CheckLable(out var name))
                throw new Exception($"语法错误！行:{_line}");
            if (!CheckBeginType())
                throw new Exception($"语法错误！行:{_line}");
            var tk = GetToken();
            while (!CheckEndType(tk))
            {
                fields.Add(BuildProtoField(tk, out tk));
            }
            return new ProtoType(name, type, fields.ToArray());
        }

        private ProtoField BuildProtoField(Token tk, out Token nextTk)
        {
            if (!CheckLable(tk, out var name))
                throw new Exception($"语法错误！行:{_line}");
            if (!CheckColon(GetToken()))
                throw new Exception($"语法错误！行:{_line}");
            if (!CheckLable(GetToken(), out var type))
                throw new Exception($"语法错误！行:{_line}");
            nextTk = GetIsArray(out var isArray);
            return new ProtoField(name, type, isArray);
        }

        private Token GetIsArray(out bool isArray)
        {
            var tk = GetToken();
            if (tk.Type == TokenType.Sign && tk.Value == "[")
            {
                tk = GetToken();
                if (tk.Type == TokenType.Sign && tk.Value == "]")
                {
                    isArray = true;
                    return GetToken();
                }
                throw new Exception($"语法错误！行:{_line}");
            }
            isArray = false;
            return tk;
        }
        private bool CheckLable(out string lable)
        {
            return CheckLable(GetToken(), out lable);
        }
        private bool CheckLable(Token tk, out string lable)
        {
            lable = null;
            if (tk.Type != TokenType.Lable)
                return false;
            lable = tk.Value;
            return true;
        }
        private bool CheckColon(Token tk)
        {
            return tk.Type == TokenType.Sign && tk.Value == ":";
        }
        private bool CheckEndType(Token tk)
        {
            return tk.Type == TokenType.Sign && tk.Value == "}";
        }
        private bool CheckBeginType()
        {
            return CheckBeginType(GetToken());
        }
        private bool CheckBeginType(Token tk)
        {
            return tk.Type == TokenType.Sign && tk.Value == "{";
        }
        private Token GetToken()
        {
            var token = new Token();
        START:
            if (Next() == -1)
                return token;
            if (WhiteSpaces.Contains((char)_current))
            {
                goto START;
            }
            if (_current == '#')
            {
                ToNextLine();
                goto START;
            }

            Append();
            if (Signs.Contains((char)_current))
            {
                token.Value = BuildLable();
                token.Type = TokenType.Sign;
                return token;
            }
        LABLE:
            if (Next() == -1)
            {
                goto RETURN_LABLE;
            }
            if (WhiteSpaces.Contains((char)_current))
            {
                goto RETURN_LABLE;
            }
            if (_current == '#')
            {
                ToNextLine();
                goto RETURN_LABLE;
            }
            if (Signs.Contains((char)_current))
            {
                Rollback();
                goto RETURN_LABLE;
            }
            Append();
            goto LABLE;
        RETURN_LABLE:
            token.Value = BuildLable();
            token.Type = TokenType.Lable;
            return token;
        }
        private void ToNextLine()
        {
            while (Next() != -1)
            {
                if (_current == '\n')
                {
                    return;
                }
            }
        }
        private int Next()
        {
            if (_index + 1 < _length)
            {
                _current = _buffer[++_index];
                if (_current == '\n')
                    _line++;
                return _current;
            }
            _current = -1;
            return -1;
        }
        private int Rollback()
        {
            if (_index - 1 >= 0)
            {
                if (_current == '\n')
                    _line--;
                _current = _buffer[--_index];
                return _current;
            }
            _current = -1;
            return -1;
        }
        private void Append()
        {
            _builder.Append((char)_current);
        }
        private string BuildLable()
        {
            var value = _builder.ToString();
            _builder.Clear();
            return value;
        }
    }
}
